from rpcs.base import Base
import requests
from utils.log.logger import Logger
from utils.exception import RpcException, CriticalException
import json
import decimal
from models import db
from models.constants import Status, TokenType
from models import constants
import time
import re


class Etp(Base):
    rpc_version = "2.0"
    rpc_id = 0

    def __init__(self, settings, tokens):
        Base.__init__(self, settings)

        self.token_mapping = json.loads(
            open('config/token_mapping.json').read())

        self.tokens = {}
        for token in tokens:
            name = token['name']
            self.tokens[name] = token

            to_tokens = self.token_mapping.get(name)
            if to_tokens:
                Logger.get().info("init type {}, tokens: {}".format(
                    name, to_tokens))

        self.tx_verify_uri = settings['tx_verify_uri']

    def start(self):
        self.best_block_number()
        return True

    def stop(self):
        return False

    def make_request(self, method, params=[]):
        req_body = {
            'id': self.rpc_id,
            'jsonrpc': self.rpc_version,
            'method': method,
            "params": params}
        res = requests.post(
            self.settings['uri'], json.dumps(req_body), timeout=constants.DEFAULT_REQUEST_TIMEOUT)
        if res.status_code != 200:
            raise RpcException('bad request code,%s' % res.status_code)
        try:
            js = json.loads(res.text)
            if isinstance(js, dict) and js.get('error') is not None:
                raise RpcException(js['error'])
            return js
        except ValueError as e:
            pass
        return res.text

    def is_mst_transfer(self, output):
        return output['attachment']['type'] == 'asset-transfer'

    def parse_token_type(self, trans):
        for j, output in enumerate(trans['outputs']):
            if self.is_mst_transfer(output):
                return TokenType.Mst

        return TokenType.Etp

    def parse_message_content(self, height, output):
        assert(output['attachment']['type'] == 'message')
        content = output['attachment']['content']
        if content and len(content) > 0:
            try:
                rst = json.loads(content)
                if (rst == None
                        or 'type' not in rst
                        or 'to' not in rst
                        or 'rate' not in rst):
                    return None, None, None

                to_token = rst['type']
                address = rst['to']
                rate = rst['rate']

                return address, to_token, rate

            except Exception as e:
                return None, None, None

        return None, None, None

    def is_valid_swap_pair(self, from_token, to_token):
        if (from_token != to_token
                and from_token in self.token_mapping
                and to_token in self.token_mapping.get(from_token)):
            return True

        return False

    def process_mst_transfer(self, scan_address, trans, input_addresses, from_address,
                             height, block_hash, index, timestamp):
        tx = {}

        nonce = 0
        for j, output in enumerate(trans['outputs']):

            # get swap info of token
            if self.is_mst_transfer(output):
                to_addr = '' if output.get(
                    'address') is None else output['address']

                # check it is scan address
                if to_addr != scan_address:
                    continue

                # check it is not from scan address
                if to_addr in input_addresses:
                    continue

                tx['nonce'] = nonce
                tx['block_hash'] = block_hash
                tx['block_height'] = height
                tx['index'] = index
                tx['hash'] = trans['hash']
                tx['output_index'] = j
                tx['time'] = int(timestamp)
                tx['input_addresses'] = input_addresses
                tx['script'] = output['script']

                tx['type'] = 'ETP'
                tx['token_type'] = int(TokenType.Mst)
                tx['swap_address'] = to_addr
                tx['from_address'] = from_address
                tx['from_token'] = output['attachment']['symbol']
                tx['value'] = int(output['attachment']['quantity'])
                tx['fee'] = self.from_wei('ETP', 10000)

            # get json content of swap target address
            elif output['attachment']['type'] == 'message':
                target_address, to_token, rate = self.parse_message_content(
                    height, output)
                if target_address and to_token and rate:
                    tx['to_address'] = target_address
                    tx['to_token'] = to_token
                    tx['rate'] = rate

        if ('from_token' in tx
                and 'to_token' in tx
                and 'to_address' in tx
                and 'rate' in tx):
            from_token = tx['from_token']
            if from_token not in self.tokens:
                return None

            tx['amount'] = self.from_wei(from_token, tx['value'])
            to_address = tx.get('to_address')
            to_token = tx['to_token']

            # check it is a valid etp address or did
            if not self.is_address_valid(to_address) and not self.is_did_valid(to_address):
                Logger.get().error("swap mst {} - {}, amount: {}, height: {}, tx_hash: {}, invalid to: {}".format(
                    from_token, to_token, tx['amount'], tx['hash'], tx['block_height'], to_address))
                tx['message'] = 'invalid to address or did:' + to_address
                tx['ban'] = True

            elif not self.is_valid_swap_pair(from_token, to_token):
                Logger.get().error("swap mst {} - {}, value: {}, height: {}, tx_hash: {}, from: {}, to: {}, invalid swap pair".format(
                    from_token, to_token, tx['amount'], tx['block_height'], tx['hash'], from_address, to_address))
                tx['message'] = 'invalid swap pair: {} - {}'.format(
                    from_token, to_token)
                tx['ban'] = True

            Logger.get().info("swap mst {} - {}, amount: {}, rate: {}, height: {}, tx_hash: {}, from: {}, to: {}".format(
                from_token, to_token, tx['amount'], rate, tx['block_height'], tx['hash'], from_address, to_address))
        else:
            tx = None

        return tx

    def get_block_by_height(self, height, scan_address):
        res = self.make_request('getblock', [height])
        transactions = res['result']['transactions']
        timestamp = res['result']['timestamp']
        block_hash = res['result']['hash']

        txs = []
        for i, trans in enumerate(transactions):
            token_type = self.parse_token_type(trans)
            if token_type == TokenType.Unknown:
                continue

            input_addresses = [input_['address'] for input_ in trans[
                'inputs'] if input_.get('address') is not None]
            input_addresses = list(set(input_addresses))
            from_address = input_addresses[0] if len(
                input_addresses) > 0 else ''

            if token_type == TokenType.Mst:
                tx = self.process_mst_transfer(
                    scan_address, trans, input_addresses, from_address,
                    height, block_hash, i, timestamp)
                if tx:
                    txs.append(tx)
            elif token_type == TokenType.Etp:
                # TODO:
                pass

        res['txs'] = txs
        return res

    def verify_tx(self, tx):

        try:
            res = requests.get(
                self.tx_verify_uri + str(tx['hash']), timeout=constants.DEFAULT_REQUEST_TIMEOUT)
            if res.status_code != 200:
                raise RpcException(
                    'bad request code: {}'.format(res.status_code))

            js = json.loads(res.text)['result']
            if js['hash'] == tx['hash']:
                if res.text.find('confirmed_at') != -1:
                    tx['block_hash'] = js['block']
                    tx['block_height'] = js['height']
                    return Status.Tx_Checked
            else:
                tx['ban'] = True
                tx['message'] = 'Check Tx failed, current: {}, verify_tx: {}'.format(
                    tx, js)
                return Status.Tx_Ban

        except Exception as e:
            Logger.get().error(
                'Failed to verify_tx: {}, error: {}'.format(tx['hash'], str(e)))

        return Status.Tx_Unchecked

    def is_address_valid(self, address):
        if address is None or address == '':
            return False

        res = self.make_request('validateaddress', [address])
        return res['result']['is_valid']

    def is_did_valid(self, did):
        if did is None or did == '':
            return False

        res = self.make_request('getdid', [did])
        if not res.get('result'):
            return False

        return True

    def is_swap(self, tx, scan_address):

        if tx['amount'] <= 0:
            return False

        if tx['from_token'] not in self.tokens:
            return False

        if scan_address in tx['input_addresses']:
            return False

        if tx['swap_address'] != scan_address:
            return False

        # prevent locking tx
        if 'numequalverify' in tx['script']:
            return False

        return True

    def get_transaction(self, txid):
        result = None
        try:
            res = self.make_request('gettransaction', [txid])
            result = res['result']
            if result:
                result['block_height'] = result['height']
        except RpcException as e:
            Logger.get().error("failed to get transaction: {}".format(str(e)))
            raise
        return result

    def best_block_number(self):
        res = self.make_request('getheight')
        return res['result']

    def get_decimal(self, symbol):
        token = self.tokens.get(symbol)
        if token:
            return token['decimal']

        raise CriticalException(
            'decimal config missing: token={}'.format(symbol))
